理解虚拟DOM(virtual DOM) 、浅谈diff算法的实现

因为今天在给一个项目做一个小demo,用到了jQuery,想起用了VUE之后就没怎么用过jQuery做DOM操作了;于是感慨到virtual DOM的好处,于是想着回家整理整理

什么是virtual DOM ?为什么存在virtual DOM?

  • 用JS模拟虚拟的DOM结构,生成虚拟的DOM,当数据更新是,对比DOM的变化,只更新需要更新的数据,从而减少"昂贵"的DOM操作;
  • 为了提高DOM重绘制的性能

HTML代码如下:
在这里插入图片描述
对应的vdom如下:(class是关键字,所以会别名叫做className)
在这里插入图片描述

vdom如何应用,核心API是什么?

使用snabbdom 来实现vdom==>dom

  • 使用snabbdom ,把一个创建好的虚拟DOM生成为浏览器页面上的真实DOM;

  • snabbdom有以下重要的API

    • h(‘<标签名>’, {…属性…}, […子元素…])
    • h(‘<标签名>’, {…属性…}, ‘….’)
    • patch(container, vnode)
    • patch(vnode, newVnode)
  • 创建一个div和一个button按钮,div放入一个虚拟dom生成的UI li 列表,点击button按钮,实现数据的diff对比更新

// html
<div id="container"></div>
<button id="changeNode">change</button>
// 全局注册
var snabbdom = window.snabbdom;

var patch = snabbdom.init([
    snabbdom_class,
    snabbdom_props,
    snabbdom_style,
    snabbdom_eventlisteners
]);
// 定义h函数
var h = snabbdom.h

// 生成VNODE
var vnode = h('ui#list', {}, [
    h('li.item', {}, 'Item 1'),
    h('li.item', {}, 'Item 2'),
])

// 获取容器
var container = document.getElementById('container');
// 第一次把VNODE全部更新到容器中去
patch(container, vnode)

// 准备新节点
var newVnode = h('ui#list', {}, [
    h('li.item', {}, 'Item 1'),
    h('li.item', {}, 'Item222 2'),
    h('li.item', {}, '33232 2'),

])

document.getElementById("changeNode").addEventListener("click", function() {
// 点击按钮,通过diff算法更新节点数据
    patch(vnode, newVnode)
})

diff算法

  • vdom中找本次DOM需要更新节点来更新
  • patch函数,首次渲染,新旧对比,打补丁更新
  • patch(container, vnode) 和 patch(vnode, newVnode)
  • createElment & updateChildren

createElment 的实现

  • 实现虚拟dom节点核心逻辑思想是:创建根节点,如果有子节点,继续递归调用createElment方法,只到把节点append进上一级节点。从而在页面上出现真实的DOM结构;
  • 对应着 snabbdom patch(container, vnode)

伪代码逻辑如下:

function createElement(vnode){
	var tag=vnode.tag
	var attr=vnode.attrs||{}
	var children=vnode.children||[]

	if(!tag){
		return null;
	}

	// 真实的DOM元素
	var elem=document.createElement(tag);
	var attrName

	for(attrName in attrs){
		elem.setAttribute(attrName,attrs[attrName]);
	}

	// 子元素
	children.forEach(childVnode=>{
		// 给Element 添加子元素,递归
		elem.appendChild(createElement(childVnode))
	});
	// 返回创建好的DOM元素
	return elem;
}

updateChildren & diff

  • diff只更新当前需要更新的数据
  • 对比节点的数据,相同则跳过,不相同replace掉久的节点
  • 对应着 snabbdom patch(vnode, newVnode)

伪代码逻辑如下:

function updateElement(vnode,newVnode){
	var children=vnode.children||[];
	var newChildren=newChildren.children||[];

	children.forEach((item,index)=>{
		var newItem=newChildren[index];
		if(item==newItem){
			// 递归深层次对比
			updateElement(item,newItem);
		}else{
			// 不同则替换
			replaceNode(item,newItem);
		}
	})

}

React的DIFF算法为什么时间复杂度是O(n)复杂度而不是常规的O(n^3)

时间复杂度的理解见我这篇文章:算法与数据结构 | 时间复杂度分析 / 更准确的描述代码的时间复杂度

为啥呢?

首先:常规的O(n^3);

  • 传统的diff需要出了上面的比较之外,还需要跨级比较。
  • 两个新旧节点的遍历是O(n^2)
  • 执行节点的更新再一次,所以为O(n^3)

React 的O(n)

  • react树diff对比是按照层级,会给树编号0,1,2,3,4… 然后相同的编号层级进行比较,所以复杂度是n

diff 算法非常复杂,以上只能算是浅谈了,了解了大概的设计逻辑;

diff还在一些场景用过,就是git 提交的时候常常 git diff XXX 看文件对比哈哈哈哈

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值