# 什么是 Virtual DOM
Virtual DOM
(虚拟 DOM),是由普通的JS
对象来描述DOM
对象,因为不是真实的DOM
对象,所以叫Virtual DOM
let element = document.querySelector('#app') let s = '' for (var key in element) { s += key + ',' } console.log(s) // 打印结果 align,title,lang,translate,dir,hidden,accessKey,draggable,spellcheck,aut ocapitalize,contentEditable,isContentEditable,inputMode,offsetParent,off setTop,offsetLeft,offsetWidth,offsetHeight,style,innerText,outerText,onc opy,oncut,onpaste,onabort,onblur,oncancel,oncanplay,oncanplaythrough,onc hange,onclick,onclose,oncontextmenu,oncuechange,ondblclick,ondrag,ondrag end,ondragenter,ondragleave,ondragover,ondragstart,ondrop,ondurationchan ge,onemptied,onended,onerror,onfocus,oninput,oninvalid,onkeydown,onkeypr ess,onkeyup,onload,onloadeddata,onloadedmetadata,onloadstart,onmousedown ,onmouseenter,onmouseleave,onmousemove,onmouseout,onmouseover,onmouseup, onmousewheel,onpause,onplay,onplaying,onprogress,onratechange,onreset,on resize,onscroll,onseeked,onseeking,onselect,onstalled,onsubmit,onsuspend ,ontimeupdate,ontoggle,onvolumechange,onwaiting,onwheel,onauxclick,ongot pointercapture,onlostpointercapture,onpointerdown,onpointermove,onpointe rup,onpointercancel,onpointerover,onpointerout,onpointerenter,onpointerl eave,onselectstart,onselectionchange,onanimationend,onanimationiteration ,onanimationstart,ontransitionend,dataset,nonce,autofocus,tabIndex,click ,focus,blur,enterKeyHint,onformdata,onpointerrawupdate,attachInternals,n amespaceURI,prefix,localName,tagName,id,className,classList,slot,part,at tributes,shadowRoot,assignedSlot,innerHTML,outerHTML,scrollTop,scrollLef t,scrollWidth,scrollHeight,clientTop,clientLeft,clientWidth,clientHeight ,attributeStyleMap,onbeforecopy,onbeforecut,onbeforepaste,onsearch,eleme ntTiming,previousElementSibling,nextElementSibling,children,firstElement Child,lastElementChild,childElementCount,onfullscreenchange,onfullscreen error,onwebkitfullscreenchange,onwebkitfullscreenerror,setPointerCapture ,releasePointerCapture,hasPointerCapture,hasAttributes,getAttributeNames ,getAttribute,getAttributeNS,setAttribute,setAttributeNS,removeAttribute ,removeAttributeNS,hasAttribute,hasAttributeNS,toggleAttribute,getAttrib uteNode,getAttributeNodeNS,setAttributeNode,setAttributeNodeNS,removeAtt ributeNode,closest,matches,webkitMatchesSelector,attachShadow,getElement sByTagName,getElementsByTagNameNS,getElementsByClassName,insertAdjacentE lement,insertAdjacentText,insertAdjacentHTML,requestPointerLock,getClien tRects,getBoundingClientRect,scrollIntoView,scroll,scrollTo,scrollBy,scr ollIntoViewIfNeeded,animate,computedStyleMap,before,after,replaceWith,re move,prepend,append,querySelector,querySelectorAll,requestFullscreen,web kitRequestFullScreen,webkitRequestFullscreen,createShadowRoot,getDestina tionInsertionPoints,ELEMENT_NODE,ATTRIBUTE_NODE,TEXT_NODE,CDATA_SECTION_ NODE,ENTITY_REFERENCE_NODE,ENTITY_NODE,PROCESSING_INSTRUCTION_NODE,COMME NT_NODE,DOCUMENT_NODE,DOCUMENT_TYPE_NODE,DOCUMENT_FRAGMENT_NODE,NOTATION _NODE,DOCUMENT_POSITION_DISCONNECTED,DOCUMENT_POSITION_PRECEDING,DOCUMEN T_POSITION_FOLLOWING,DOCUMENT_POSITION_CONTAINS,DOCUMENT_POSITION_CONTAI NED_BY,DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC,nodeType,nodeName,baseU RI,isConnected,ownerDocument,parentNode,parentElement,childNodes,firstCh ild,lastChild,previousSibling,nextSibling,nodeValue,textContent,hasChild Nodes,getRootNode,normalize,cloneNode,isEqualNode,isSameNode,compareDocu mentPosition,contains,lookupPrefix,lookupNamespaceURI,isDefaultNamespace ,insertBefore,appendChild,replaceChild,removeChild,addEventListener,remo veEventListener,dispatchEvent
@程序员poetry: 代码已经复制到剪贴板
- 可以使用
Virtual DOM
来描述真实DOM
{ sel: "div", data: {}, children: undefined, text: "Hello Virtual DOM", elm: undefined, key: undefined }
@程序员poetry: 代码已经复制到剪贴板
# 为什么使用 Virtual DOM
- 手动操作
DOM
比较麻烦,还需要考虑浏览器兼容性问题,虽然有jQuery
等库简化DOM
操作,但是随着项目的复杂 DOM 操作复杂提升 - 为了简化
DOM
的复杂操作于是出现了各种MVVM
框架,MVVM
框架解决了视图和状态的同步问题 - 为了简化视图的操作我们可以使用模板引擎,但是模板引擎没有解决跟踪状态变化的问题,于是
Virtual DOM
出现了 Virtual DOM
的好处是当状态改变时不需要立即更新 DOM,只需要创建一个虚拟树来描述DOM
,Virtual DOM
内部将弄清楚如何有效(diff
)的更新DOM
- 虚拟
DOM
可以维护程序的状态,跟踪上一次的状态 - 通过比较前后两次状态的差异更新真实
DOM
# 虚拟 DOM 的作用
- 维护视图和状态的关系
- 复杂视图情况下提升渲染性能
- 除了渲染
DOM
以外,还可以实现SSR(Nuxt.js/Next.js)
、原生应用(Weex/React Native
)、小程序(mpvue/uni-app
)等
# Virtual DOM 库
- Snabbdom (opens new window)
Vue 2.x
内部使用的Virtual DOM
就是改造的Snabbdom
- 通过模块可扩展
- 源码使用
TypeScript
开发 - 最快的
Virtual DOM
之一
- virtual-dom (opens new window)
# Snabbdom 基本使用
# 创建项目
# 创建项目目录 md snabbdom-demo # 进入项目目录 cd snabbdom-demo # 创建 package.json yarn init -y # 本地安装 parcel yarn add parcel-bundler
@程序员poetry: 代码已经复制到剪贴板
配置 package.json
的 scripts
"scripts": { "dev": "parcel index.html --open", "build": "parcel build index.html" }
@程序员poetry: 代码已经复制到剪贴板
创建目录结构
yarn add snabbdom
@程序员poetry: 代码已经复制到剪贴板
import{init,h,thunk}from'snabbdom'
@程序员poetry: 代码已经复制到剪贴板
snabbdom
的核心仅提供最基本的功能,只导出了三个函数init()
、h()
、thunk()
init()
是一个高阶函数,返回patch()
h()
返回虚拟节点VNode
,这个函数我们在使用Vue.js
的时候见过
new Vue({ router, store, render: h => h(App) }).$mount('#app')
@程序员poetry: 代码已经复制到剪贴板
thunk()
是一种优化策略,可以在处理不可变数据时使用
注意:导入时候不能使用
import snabbdom from 'snabbdom'
。原因:node_modules/src/snabbdom.ts
末尾导出使用的语法是export
导出API
,没有使用export default
导出默认输出
# 基本使用
例子1
import { h, init } from 'snabbdom' // 1. hello world // 参数:数组,模块 // 返回值:patch函数,作用对比两个vnode的差异更新到真实DOM let patch = init([]) // 第一个参数:标签+选择器 // 第二个参数:如果是字符串的话就是标签中的内容 let vnode = h('div#container.cls', { hook: { init (vnode) { console.log(vnode.elm) }, create (emptyVnode, vnode) { console.log(vnode.elm) } } }, 'Hello World') let app = document.querySelector('#app') // 第一个参数:可以是DOM元素,内部会把DOM元素转换成VNode // 第二个参数:VNode // 返回值:VNde let oldVnode = patch(app, vnode) // 假设的时刻 vnode = h('div', 'Hello Snabbdom') patch(oldVnode, vnode)
@程序员poetry: 代码已经复制到剪贴板
例子2
// 2. div中放置子元素 h1,p import { h, init } from 'snabbdom' let patch = init([]) let vnode = h('div#container', [ h('h1', 'Hello Snabbdom'), h('p', '这是一个p标签') ]) let app = document.querySelector('#app') let oldVnode = patch(app, vnode) setTimeout(() => { vnode = h('div#container', [ h('h1', 'Hello World'), h('p', 'Hello P') ]) patch(oldVnode, vnode) // 清空页面元素 -- 错误 // patch(oldVnode, null) patch(oldVnode, h('!')) }, 2000);
@程序员poetry: 代码已经复制到剪贴板
例子3 debug-patchVnode
import { h, init } from 'snabbdom' let patch = init([]) // 首次渲染 let vnode = h('div', 'Hello World') let app = document.querySelector('#app') let oldVnode = patch(app, vnode) // patchVnode 的执行过程 vnode = h('div', 'Hello Snabbdom') patch(oldVnode, vnode)
@程序员poetry: 代码已经复制到剪贴板
例子4 debug-updateChildren
import { h, init } from 'snabbdom' let patch = init([]) // 首次渲染 let vnode = h('ul', [ h('li', '首页'), h('li', '视频'), h('li', '微博') ]) let app = document.querySelector('#app') let oldVnode = patch(app, vnode) // updateChildren 的执行过程 vnode = h('ul', [ h('li', '首页'), h(
@程序员poetry: 代码已经复制到剪贴板
阅读全文