为什么使用虚拟DOM

虚拟DOM不会进行排版与重绘操作 虚拟DOM进行频繁修改,然后一次性比较并修改真实DOM中需要改的部分(注意!),最后并在真实DOM中进行排版与重绘,减少过多DOM节点排版与重绘损耗 真实DOM频繁排版与重绘的效率是相当低的 虚拟DOM有效降低大面积(真实DOM节点)的重绘与排版,因为最终与真实DOM比较差异,可以只渲染局部(同2) 使用虚拟DOM的损耗计算: 总损耗=虚拟DOM增删改+(与Diff算法效率有关)真实DOM差异增删改+(较少的节点)排版与重绘 直接使用真实DOM的损耗计算: 总损耗=真实DOM完全增删改+(可能较多的节点)排版与重绘 总之,一切为了减弱频繁的大面积重绘引发的性能问题,不同框架不一定需要虚拟DOM,关键看框架是否频繁会引发大面积的DOM操作。

虚拟DOM优化实战

如果我有1000条数据,我修改了其中两条,真实的DOM会重新渲染1000条数据,只要发生了变化,就会重新渲染全部数据,虚拟dom 会生成1000个对象 (它是不会被浏览器图形化渲染的),虚拟dom 里的东西会和真实dom绑定在一起,当数据发生变化 虚拟dom和之前的虚拟dom 会去做数据的比较,当数据发生变化时,才会去更新数据发生改变的那部分真实的dom元素
但是数组没有默认的标识,所以数组每次改变都要重新排序,性能影响较大,所以在实时侦听遍历数组数据时,需要引入key属性,用来标识数组数据,一般使用下标标识

虚拟dom的好处

  1. 虚拟DOM就是用JS对象来表示或者是模拟一个真实DOM的结构
  2. 页面性能 通过虚拟DOM的对比,进行差异的更新能提升页面的性能.

如何使用虚拟dom

  1. 如何描述虚拟DOM(create)
  2. 如何绘制虚拟DOM(render)
  3. 如何差异化的更新虚拟DOM并且更新UI(update)

demo实战

其实就是一个JS的对象。我们先来简单的实现一个虚拟DOM。我们使用过React的都知道createElement这一个函数、Vue都知道render或者是“h”,这样一个函数。我们先来创建一个createElement函数。

  1. /**
  2. * [createElement 用来创建DOM节点]
  3. * @param {[type]} type [元素类型(名称)]
  4. * @param {[type]} props [描述信息]
  5. * @param {[type]} children [子节点]
  6. * @return {[type]} [description]
  7. */
  8. function createElement(type, props, children) {
  9. // 返回一个Element对象。
  10. return new Element(type, props, children);
  11. }
  12. 复制代码
  1. <div class="vdom">561651</div>
  2. type: div
  3. props: class="vdom"
  4. children: 561651
  5. 复制代码

元素对象(Element),用来表示一个元素。

  1. class Element {
  2. constructor(type, props, children) {
  3. this.type = type;
  4. this.props = props;
  5. this.children = children;
  6. }
  7. }
  8. 复制代码
  1. let vDom = createElement("ul", {class: "dawd"}, [
  2. createElement("li", {class: "dawd"}, ["1"]),
  3. createElement("li", {class: "dawd"}, ["2"]),
  4. createElement("li", {class: "dawd"}, ["3"])
  5. ]);
  6. 复制代码

虚拟DOM - 图1已经基本描述出了DOM的树形结构。下面我们来根据虚拟DOM创建真实的DOM。在这之前我们先创建元素节点(单个元素)。

  1. /**
  2. * [createNode 创建单个元素]
  3. * @param {[type]} node [元素节点]
  4. * @return {[type]} [真实的DOM元素]
  5. */
  6. function createNode(node){
  7. // 根据类型创建元素
  8. let el = document.createElement(node.type);
  9. for (key in node.props) {
  10. // 遍历属性
  11. if(key === "value"){
  12. // 只有input还有textarea需要value属性
  13. if(node.type.toUpperCase() === "INPUT" || node.type.toUpperCase() === "TEXTAREA"){
  14. el.value = node.props[key];
  15. }
  16. }else {
  17. // 设置属性
  18. el.setAttribute(key, node.props[key]);
  19. }
  20. }
  21. return el;
  22. }
  23. 复制代码

根据单个元素组成DOM树

  1. function createDom(node) {
  2. let root = createNode(node);
  3. if(node.children && node.children.length > 0){
  4. // 遍历子元素
  5. node.children.forEach( function(element) {
  6. if(element instanceof Element){
  7. // 节点
  8. root.appendChild( createDom(element) );
  9. }else {
  10. // 文本
  11. root.appendChild( document.createTextNode(element) );
  12. }
  13. });
  14. }
  15. return root;
  16. }
  17. 复制代码

根据虚拟DOM生成的真实DOM
现在只是生成了真实的DOM但是还没有真正的挂载到DOM树上,没有显示。

  1. let dom = createDom(vDom);
  2. document.getElementsByTagName("body")[0].appendChild(dom);
  3. 复制代码

虚拟DOM - 图2
再试一下input元素

  1. let vDom = createElement("ul", {class: "dawd"}, [
  2. createElement("li", {class: "dawd"}, [
  3. createElement("input", {type: "radio",value: "1651"},[]),
  4. createElement("input", {type: "text",value: "1651"},[])
  5. ])
  6. ]);
  7. 复制代码

真实DOM
显示效果虚拟DOM - 图3
我们再来一个复杂一点的来验证是否正确。

  1. createElement("div", {class: "div"}, [
  2. createElement("ul", {class: "ul"}, [
  3. createElement("li", {class: "li"},[createElement("input", {type: "radio",value: "1651"},["单选"])]),
  4. createElement("li", {class: "li"},[createElement("input", {type: "text",value: "1651"},[])]),]),
  5. createElement("div", {class: "div"}, [
  6. createElement("p", {class: "p"},[
  7. createElement("span", {class: "span"},["我是span"])]),
  8. createElement("a", {class: "a",href: "https://juejin.im/editor/drafts/5cf3c75de51d45572c05fff3"},[
  9. createElement("span", {class: "span"},["我是超链接里面的span"])]),
  10. createElement("img", {class: "img",src: "http://g.hiphotos.baidu.com/image/h%3D300/sign=b5e4c905865494ee982209191df4e0e1/c2cec3fdfc03924590b2a9b58d94a4c27d1e2500.jpg",alt: "虚拟DOM图片",title: "虚拟的DOM"},[])
  11. ]),
  12. ]);
  13. 复制代码

虚拟DOM - 图4

  • 总结
    • 虚拟DOM对我们的项目性能很有帮助,我们重点的是要了解他的思想和实现,不是一味的使用或者是照抄。
    • 说到虚拟DOM就离不开DOM diff算法,我打算把他们分了两篇文章来写,首先一篇文章会太长。其次,我觉得这两个概念虽然戚戚相关但是我觉得还是两个东西。diff算法我觉得是对DOM更新等操作的优化,减少无用的更新,这样会带来更少的消耗更多的性能。