深入了解virtual dom

前言

vdom 是 vue 和 React 的核心,先讲哪个都绕不开它

vdom 比较独立,使用也比较简单

如果面试问到 vue 和 React 和实现,免不了问 vdom,带着三个问题去深入了解

问题

  • vdom 是什么?为何会存在 vdom?

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

  • 介绍一下 diff 算法

1、vdom 是什么?为何会存在 vdom?

  • virtual dom,虚拟 DOM

  • 用JS模拟DOM的结构

  • DOM 变化的对比,放在 JS 层来做(图灵完备语言)

  • 提高重绘性能

DOM结构

 
  1. <ul id='list'>

  2. <li class='item'>item1</li>

  3. <li class='item'>item2</li>

  4. </ul>

JS模拟

 
  1. {

  2. tag:'ul',

  3. attrs:{

  4. id:'list'

  5. },

  6. children:[

  7. {

  8. tag:'li',

  9. attrs:{

  10. className: 'item'

  11. },

  12. children:['item1']

  13. },{

  14. tag:'li',

  15. attrs:{

  16. className: 'item'

  17. },

  18. children:['item2']

  19. }

  20. ]

  21. }

设计一个需求场景 

 用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

 
  1. // <script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom.js"></script>

  2. // <script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-class.js"></script>

  3. // <script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-props.js"></script>

  4. // <script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-style.js"></script>

  5. // <script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-eventlisteners.js"></script>

  6. // <script src="https://cdn.bootcss.com/snabbdom/0.7.1/h.js"></script>

  7. var snabbdom = window.snabbdom

  8. // 定义 patch

  9. var patch = snabbdom.init([

  10. snabbdom_class,

  11. snabbdom_props,

  12. snabbdom_style,

  13. snabbdom_eventlisteners

  14. ])

  15. // 定义 h

  16. var h = snabbdom.h

  17. var container = document.getElementById('container')

  18. // 生成 vnode

  19. var vnode = h('ul#list', {}, [

  20. h('li.item', {}, 'Item 1'),

  21. h('li.item', {}, 'Item 2')

  22. ])

  23. patch(container, vnode)

  24. document.getElementById('btn-change').addEventListener('click', function () {

  25. // 生成 newVnode

  26. var newVnode = h('ul#list', {}, [

  27. h('li.item', {}, 'Item 1'),

  28. h('li.item', {}, 'Item B'),

  29. h('li.item', {}, 'Item 3')

  30. ])

  31. patch(vnode, newVnode) // 找出差异,渲染差异

  32. })

 
  1. // jquery例子改造

  2. var snabbdom = window.snabbdom

  3. // 定义关键函数 patch

  4. var patch = snabbdom.init([

  5. snabbdom_class,

  6. snabbdom_props,

  7. snabbdom_style,

  8. snabbdom_eventlisteners

  9. ])

  10. // 定义关键函数 h

  11. var h = snabbdom.h

  12. // 原始数据

  13. var data = [{

  14. name: '张三',

  15. age: '20',

  16. address: '北京'

  17. },

  18. {

  19. name: '李四',

  20. age: '21',

  21. address: '上海'

  22. },

  23. {

  24. name: '王五',

  25. age: '22',

  26. address: '广州'

  27. }

  28. ]

  29. // 把表头也放在 data 中

  30. data.unshift({

  31. name: '姓名',

  32. age: '年龄',

  33. address: '地址'

  34. })

  35. var container = document.getElementById('container')

  36. // 渲染函数

  37. var vnode

  38. function render(data) {

  39. var newVnode = h('table', {}, data.map(function (item) {

  40. var tds = []

  41. var i

  42. for (i in item) {

  43. if (item.hasOwnProperty(i)) {

  44. tds.push(h('td', {}, item[i] + ''))

  45. }

  46. }

  47. return h('tr', {}, tds)

  48. }))

  49. if (vnode) {

  50. // re-render

  51. patch(vnode, newVnode)

  52. } else {

  53. // 初次渲染

  54. patch(container, newVnode)

  55. }

  56. // 存储当前的 vnode 结果

  57. vnode = newVnode

  58. }

  59. // 初次渲染

  60. render(data)

  61. var btnChange = document.getElementById('btn-change')

  62. btnChange.addEventListener('click', function () {

  63. data[1].age = 30

  64. data[2].address = '深圳'

  65. // re-render

  66. render(data)

  67. })

  • 使用 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

 
  1. // diff 算法实现

  2. // code demo

  3. function createElement(vnode) {

  4. var tag = vnode.tag // 'ul'

  5. var attrs = vnode.attrs || {}

  6. var children = vnode.children || []

  7. if (!tag) {

  8. return null

  9. }

  10. // 创建真实的 DOM 元素

  11. var elem = document.createElement(tag)

  12. // 属性

  13. var attrName

  14. for (attrName in attrs) {

  15. if (attrs.hasOwnProperty(attrName)) {

  16. // 给 elem 添加属性

  17. elem.setAttribute(attrName, attrs[attrName])

  18. }

  19. }

  20. // 子元素

  21. children.forEach(function (childVnode) {

  22. // 给 elem 添加子元素

  23. elem.appendChild(createElement(childVnode)) // 递归

  24. })

  25. // 返回真实的 DOM 元素

  26. return elem

  27. }

  28. // vnode newVnode compare

  29. function updateChildren(vnode, newVnode) {

  30. var children = vnode.children || []

  31. var newChildren = newVnode.children || []

  32. children.forEach(function (childVnode, index) {

  33. var newChildVnode = newChildren[index]

  34. if (childVnode.tag === newChildVnode.tag) {

  35. // 深层次对比,递归

  36. updateChildren(childVnode, newChildVnode)

  37. } else {

  38. // 替换

  39. replaceNode(childVnode, newChildVnode)

  40. }

  41. })

  42. }

  43. function replaceNode(vnode, newVnode) {

  44. var elem = vnode.elem // 真实的 DOM 节点

  45. var newElem = createElement(newVnode)

  46. // 替换

  47. }

  • 节点新增和删除

  • 节点重新排序

  • 节点属性、样式、事件变化

  • 如何极致压榨性能

  • ......

answer:

  • 知道什么是 diff 算法,是 linux 的基础命令

  • vdom 中应用 diff 算法是为了找出需要更新的节点

  • vdom 实现过程,createElement 和 updateChildren

  • 与核心函数 patch 的关系

原文https://mp.weixin.qq.com/s/SslkegMqtTRERbCiiO5YiA

猜你喜欢

转载自blog.csdn.net/sinat_17775997/article/details/88287074