前言
vdom 是 vue 和 React 的核心,先讲哪个都绕不开它
vdom 比较独立,使用也比较简单
如果面试问到 vue 和 React 和实现,免不了问 vdom,带着三个问题去深入了解
问题
- vdom 是什么?为何会存在 vdom?
- vdom 的如何应用,核心 API 是什么
- 介绍一下 diff 算法
1、vdom 是什么?为何会存在 vdom?
- virtual dom,虚拟 DOM
- 用JS模拟DOM的结构
- DOM 变化的对比,放在 JS 层来做(图灵完备语言)
- 提高重绘性能
DOM结构
- item1
- item2
JS模拟
{ tag:'ul', attrs:{ id:'list' }, children:[ { tag:'li', attrs:{ className: 'item' }, children:['item1'] },{ tag:'li', attrs:{ className: 'item' }, children:['item2'] } ]}复制代码
设计一个需求场景
用jQuery实现遇到的问题
- DOM的操作是“昂贵”的,js运行效率高
- 尽量减少DOM的操作,而不是推倒重来
- 项目越复杂,影响越严重
- vdom即可解决这些问题
问题解答
- virtual dom , 虚拟 DOM
- 用 JS 模拟 DOM 结构
- DOM 操作非常“昂贵”
- 将 DOM 对比操作放在 JS 层,提高效率
2、vdom 的如何应用,核心 API 是什么
- 介绍 snabbdom (vdom的一个库)
- 重做之前的 demo
- 核心 API
snabbdom 一个注重简单性、模块化、强大功能和性能的虚拟DOM库。
介绍 snabbdom - h 函数 介绍 snabbdom - patch 函数重做demo
// // // // // // var snabbdom = window.snabbdom// 定义 patchvar patch = snabbdom.init([ snabbdom_class, snabbdom_props, snabbdom_style, snabbdom_eventlisteners])// 定义 hvar h = snabbdom.hvar container = document.getElementById('container')// 生成 vnodevar vnode = h('ul#list', {}, [ h('li.item', {}, 'Item 1'), h('li.item', {}, 'Item 2')])patch(container, vnode)document.getElementById('btn-change').addEventListener('click', function () { // 生成 newVnode var newVnode = h('ul#list', {}, [ h('li.item', {}, 'Item 1'), h('li.item', {}, 'Item B'), h('li.item', {}, 'Item 3') ]) patch(vnode, newVnode) // 找出差异,渲染差异})复制代码
// jquery例子改造var snabbdom = window.snabbdom// 定义关键函数 patchvar patch = snabbdom.init([ snabbdom_class, snabbdom_props, snabbdom_style, snabbdom_eventlisteners])// 定义关键函数 hvar h = snabbdom.h// 原始数据var data = [{ name: '张三', age: '20', address: '北京' }, { name: '李四', age: '21', address: '上海' }, { name: '王五', age: '22', address: '广州' }]// 把表头也放在 data 中data.unshift({ name: '姓名', age: '年龄', address: '地址'})var container = document.getElementById('container')// 渲染函数var vnodefunction render(data) { var newVnode = h('table', {}, data.map(function (item) { var tds = [] var i for (i in item) { if (item.hasOwnProperty(i)) { tds.push(h('td', {}, item[i] + '')) } } return h('tr', {}, tds) })) if (vnode) { // re-render patch(vnode, newVnode) } else { // 初次渲染 patch(container, newVnode) } // 存储当前的 vnode 结果 vnode = newVnode}// 初次渲染render(data)var btnChange = document.getElementById('btn-change')btnChange.addEventListener('click', function () { data[1].age = 30 data[2].address = '深圳' // re-render render(data)})复制代码
- 使用 data 生成 vnode
- 第一次渲染,将 vnode 渲染到 #container 中
- 并将 vnode 缓存下来
- 修改 data 之后,用新 data 生成 newVnode
- 将 vnode 和 newVnode 对比
核心API:h 函数、patch 函数
- h(‘<标签名>’, {…属性…}, […子元素…])
- h(‘<标签名>’, {…属性…}, ‘….’)
- patch(container, vnode)
- patch(vnode, newVnode)
介绍一下 diff 算法
什么是diff算法
- linux diff 命令
- git diff (对比两个文件之间差异)
去繁就简
- diff 算法非常复杂,实现难度很大,源码量很大
- 去繁就简,讲明白核心流程,不关心细节
- 面试官也大部分都不清楚细节,但是很关心核心流程
- 去繁就简之后,依然具有很大挑战性,并不简单
vdom 为何用 diff 算法
- DOM 操作是“昂贵”的,因此尽量减少 DOM 操作
- 找出本次 DOM 必须更新的节点来更新,其他的不更新
- 这个“找出”的过程,就需要 diff 算法
diff 算法的实现流程
- patch(container, vnode)
- patch(vnode, newVnode)
核心逻辑:createElement 和 updateChildren
// diff 算法实现// code demofunction createElement(vnode) { var tag = vnode.tag // 'ul' var attrs = vnode.attrs || {} var children = vnode.children || [] if (!tag) { return null } // 创建真实的 DOM 元素 var elem = document.createElement(tag) // 属性 var attrName for (attrName in attrs) { if (attrs.hasOwnProperty(attrName)) { // 给 elem 添加属性 elem.setAttribute(attrName, attrs[attrName]) } } // 子元素 children.forEach(function (childVnode) { // 给 elem 添加子元素 elem.appendChild(createElement(childVnode)) // 递归 }) // 返回真实的 DOM 元素 return elem}// vnode newVnode comparefunction updateChildren(vnode, newVnode) { var children = vnode.children || [] var newChildren = newVnode.children || [] children.forEach(function (childVnode, index) { var newChildVnode = newChildren[index] if (childVnode.tag === newChildVnode.tag) { // 深层次对比,递归 updateChildren(childVnode, newChildVnode) } else { // 替换 replaceNode(childVnode, newChildVnode) } })}function replaceNode(vnode, newVnode) { var elem = vnode.elem // 真实的 DOM 节点 var newElem = createElement(newVnode) // 替换}复制代码
- 节点新增和删除
- 节点重新排序
- 节点属性、样式、事件变化
- 如何极致压榨性能
- ......
answer:
- 知道什么是 diff 算法,是 linux 的基础命令
- vdom 中应用 diff 算法是为了找出需要更新的节点
- vdom 实现过程,createElement 和 updateChildren
- 与核心函数 patch 的关系