共计 4155 个字符,预计需要花费 11 分钟才能阅读完成。
Virtual Dom
- vdom 是 vue 和 react 的核心
- vdom 是什么东西,有什么用,为什么会存在 vdom?
- vdom 如何应用,核心 API 是什么?
- diff 算法
## 什么是 vdom ##
- 用 js 模拟 DOM 结构
- DOM 变化的对比,放在 JS 层来做
- 提高重绘性能
<ul id="list">
<li class="item">Item 1</li>
<li class="item">Item 2</li>
</ul>
用 js 来模拟
{
tag:"ul",
attrs:{id:"list"},
children:[
{
tag:"li",
attrs:{className: "item"}, //class 是 js 的保留字,所以用 className
children:['Item 1']
},{
tag:"li",
attrs:{className: "item"},
children:['Item 2']
}
]
}
设计一个需求场景,渲染一个数组成表格
//Jquery 的实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script type="text/javascript" src="https://cdn.bootcss.com/jquery/3.4.0/jquery.min.js"></script>
<script type="text/javascript">
var dataList = [
{
name:'111',
age:1
},{
name:'222',
age:2
},{
name:'333',
age:3
},{
name:'444',
age:4
},
]
$(document).ready(function () {function render(data){var $container = $('#container')
$container.html('')
// 拼接 tabel
$table = $('<table>')
$table.append($('<tr><td>name</td><td>age</td></tr>'))
// 渲染到页面
data.forEach(item => {$table.append($(`<tr><td>${item.name}</td><td>${item.age}</td></tr>`))
});
$container.append($table)
}
render(dataList)
$("#btn-change").click(function(){dataList[1].age=30 // 每次修改数据都会清空 dom,然后重绘表格
render(dataList)
})
})
</script>
</head>
<body>
<div id="container"></div>
<button id="btn-change"> 修改数据 </button>
</body>
</html>
上述办法遇到的问题
- js 原生或者是 Jquery 框架时代,都是直接操作 DOM 节点来进行渲染页面,可是这样的代价确实是很大,需要将原本的 DOM 全部清除,然后在重新渲染一遍
- 操作 Dom 非常昂贵。每个 Dom 自带了太多的属性。js 运行效率高
- 尽量减少 Dom 操作
- 项目越复杂,运行效率越低,影响越严重
- vdom 可以解决这个问题,将 Dom 操作方在 Js 层,提高效率
vdom 如何应用,核心 API
-
snabbdom
为什么是 snabbdom.js
由于虚拟 dom 有那么多的好处而且现代前端框架中 react 和 vue 均不同程度的使用了虚拟 dom 的技术,因此通过一个简单的 库赖学习虚拟 dom 技术就十分必要了,至于为什么会选择 snabbdom.js 这个库呢?原因主要有两个:源码简短,总体代码行数不超过 500 行。
著名的 vue 的虚拟 dom 实现也是参考了 snabbdom.js 的实现。- 用 snabbdomjs 实现上述例子
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-class.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-props.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-style.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-eventlisteners.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.1/h.min.js"></script>
</head>
<body>
<div id="container"></div>
<button id="btn-change"> 修改数据 </button>
<script type="text/javascript">
var snabbdom = window.snabbdom
// 定义 patch
var patch = snabbdom.init([
snabbdom_class,
snabbdom_props,
snabbdom_style,
snabbdom_eventlisteners
])
var h = snabbdom.h
var container = document.getElementById("container")
// 生成 vnode
var vnode= h('ul#list',{},[h('li.item',{},'Item 1'),
h('li.item',{},'Item 2')
])
patch(container,vnode)
document.getElementById("btn-change").addEventListener('click',function(){console.log("111")
var newVnode = h('ul#list',{},[h('li.item',{},'Item 1'),
h('li.item',{},'Item B'),
h('li.item',{},'Item 3')
])
patch(vnode,newVnode)
})
</script>
</body>
</html>
// 修改数据只是修改了 第二个 item 第三,第一个数据没变化(F12 查看 Element 第一个 item 没有闪烁)
diff 算法
- 什么是 diff 算法
- 去繁就简
- vdom 为何用 diff 算法
- diff 算法的实现流程
diff 命令是 linux 系统自带的基础命令
git diff 判断文本文件哪里被修改了
diff 算法一直都在,并不是因为 react、vue 才出现的
vdom 为何使用 diff 算法
- DOM 操作是昂贵的,因此尽量减少 DOM 操作
- 找出本次 DOM 必须更新的节点来更新,其他的不更新
- 这个找出的过程,就需要 diff 算法
diff 实现过程
只需要明白
- path(container,vnode)
- path(vnode,newnode)
通过 VNode 创建一个真实的 DOM 的流程
function createElement(vnode){
var tag= vnode.tag
var attrs = vnode.attrs||{}
var children = vnode.children || []
if(!tag){return null}
var elem = document.createElement(tag)
var attrName
for(attrName in attrs){if(attrs.hasOwnProperty(attrName)){elem.setAttribute(attrName,attrs[attrName])
}
}
children.forEach(childNode => {elem.appendChild(createElement(childNode))
});
// 返回真实的 Dom
return elem
}
path(vnode,newVnode) 的实现,
function updateChildren(vnode,newVnode){var children = vnode.children || []
var newChildren = newVnode.children || []
// 遍历现有的 children
children.forEach((child,index)=> {var newChild = newChildren[index]
if(newChild == null){return}
if(child.tag === newChild.tag){updateChildren(child,newChild)
}else{replaceNode(child,newChild)
}
});
}
function replaceNode(vnode,newVnode){
var elem = vnode.elem
var newElem = createElement(newVnode)
}
不仅仅是以上的内容,还有以下的内容
- 节点新增和删除
- 节点重新排序
- 节点属性、样式、事件绑定
正文完
发表至: javascript
2019-04-25