1. diff算法是什么
diff
算法,即差异查找算法,而对于计算两颗树的差异时间复杂度为O(n^3)
,显然成本太高;diff
算法将其降至了O(n)
的复杂度。
在页面当中,如果我们仅仅只是新增、修改某个标签,大可只更新修改了的部分,而不是使整个dom树都重新渲染,如果很小的更改都重新渲染,那么这是对性能和资源的浪费。所以我们可以找到新旧dom树
当中差异的地方进行重新渲染。在目前流行的框架中,vue
和react
都采用了虚拟dom和diff算法相结合, 计算出虚拟DOM
中真正需要变化、渲染的部分,从而保障页面的高效渲染。
2. 虚拟Dom
在传统的开发模式中,我们如果利用原生JS或者第三方库操作dom节点,浏览器会从构建dom树从头到尾执行流程,引起页面的重绘和重排,增性能开销, 降低页面渲染速度。频繁操作dom会造成页面卡顿、影响用户体验。Virtual Dom
是一颗以js对象作为基础的树,利用对象属性来描述节点。简单来说,虚拟dom就是对真实的dom的抽象,dom中的一切属性都在虚拟dom中有对应的属性,通过操作这个对象来映射到真实的dom上。
- 真实的dom
<div class="box">
<ul>
<li>测试</li>
</ul>
</div>
- Virtaul Dom(虚拟dom)
{
sel: "div",
data: {
class: {
"box": true}
},
children: [
sel: 'ul',
data: {
},
children: [
{
sel: 'li', data: {
}, text: "测试"}
]
]
}
3. snabbdom
3.1 简介
snabbdom
是著名的虚拟DOM库,diff算法的鼻祖,vue
源码就是借鉴了该库。snabbdom
库采用了typescript,如果需要查看js版本的,可以利用npm i -S snabbdom
下载下来。build
里就含有js版本的源码。
git
地址:snabbdom库git地址
3.2 测试环境搭建
- 由于
snabbdom
库是dom
库,所以需要搭建webpack
和webpack-dev-server
开发环境。 - 注意:只能安装
webpack@5
,否则无法识别package.json
中export
- 安装命令:
npm i -S snabbdom
npm i -D webpack@5 webpack-cli@3 webpack-dev-server@3
- 配置
webpack.config.js
文件:const path = require('path') module.exports = { entry: './src/index.js', output: { publicPath: 'xuni', // 打包出来的文件名 filename: 'bundle.js', }, devtool: 'eval-cheap-module-source-map', // 用于调试 devServer: { // 配置端口 port: 8080, contentBase: 'www', }, }
- 新建目录
src
- 新建文件
src/index.js
- 新建目录
www
,并且目录下新建index.html
- 因为使用了
webpack
打包,所以需要在index.html
中引入虚拟打包路径
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="container"></div>
<!-- 点击按钮要进行新的patch -->
<button id="btn">按我改变DOM</button>
<button id="btn1">按我进行精细化比较</button>
<!--引入虚拟打包路径 -->
<script src="/xuni/bundle.js"></script>
</body>
</html>
- 终端运行
npm run dev
, 便可以启动项目
3.3 h 函数
- h函数主要用来产生虚拟节点(vnode)
3.4 vnode
- 一个将传入的参数组装成描述dom的对象并且返回,
src
中创建一个snabbdom
文件夹,用于存放相关方法。 - 创建vnode文件
vnode
源码
/**
* 用于将node转换成vnode(节点-> 虚拟节点)
* @param {*} sel 选择器
* @param {*} data 数据
* @param {*} children 孩子节点
* @param {*} text 文本
* @param {*} elm 元素
*/
export default function vnode(sel, data, children, text, elm) {
const {
key } = data
return {
sel, // selector选择器,节点类型
data, // 属性样式等
children,// 子节点,undefined表示没有子节点
text, // 节点中的文字
elm, // 改元素对应的真正的dom节点,undefined,代表目前还未被挂载到页面上
key, // 节点的唯一标识,进行性能优化
}
3.5 手写h函数
- h函数用于将外部调用传入的参数转换成
vnode
的形式。在snabbdom
当中利用了ts
函数重载的功能,定义了函数可以接收多种不同的参数,并且实现。 - 手写的h函数只支持三个参数,用于理解h函数。
- 首先会判断参数是否有三个,分别是
selector
,data
属性,c
内容 - 参数三可能具有不同的类型,所以要对参数三进行判断。
- 如果是字符串或者数字,直接调用
vnode
函数生成虚拟节点; - 如果是数组的形式,循环遍历数组,并且将遍历者加入到
children
中,返回虚拟节点; - 如果第三个函数时虚拟函数,那么说明传入的c是唯一的
children
- h函数代码如下
// 引入虚拟dom节点类
import vnode from './vnode'
/**
* h函数用于将传入标签、属性、内容转换成vnode的形式
* 形态①:h('div', {}, '文字')
形态②:h('div', {}, [])
形态③:h('div', {}, h())
* @param {*} sel 标签
* @param {*} data 属性
* @param {*} c 内容
*/
export default function h(sel, data, c) {
if (arguments.length !== 3) {
throw new Error('对不起,h函数必须传入3个参数')
}
// 检查参数3的内容是字符串内容还是数字类型
if (typeof c ==